package org.opensourcephysics.display;

import javax.swing.JOptionPane;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.StreamPrintServiceFactory;
import javax.print.SimpleDoc;
import javax.print.StreamPrintService;
import javax.print.attribute.standard.OrientationRequested;
import javax.print.PrintException;
import java.io.IOException;
import javax.print.event.PrintJobAdapter;
import javax.print.Doc;
import javax.print.PrintService;
import java.io.File;
import java.awt.Graphics2D;
import java.awt.Graphics;
import javax.print.ServiceUI;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.standard.Chromaticity;
import javax.print.PrintServiceLookup;
import javax.swing.JFileChooser;
import java.awt.Component;
import java.awt.Dimension;
import javax.swing.JDialog;
import javax.print.DocFlavor;
import javax.print.DocPrintJob;
import javax.print.event.PrintJobEvent;
import java.awt.print.Printable;
import java.awt.print.PageFormat;
import java.io.FileOutputStream;
/**
 * Print utilities for OSP componets.
 *
 * @author Wolfgang Christian
 * @version 1.0
 */
public class PrintUtils{
   private PrintUtils(){
   }


   // This method is like print() above but prints to a PostScript file
   // instead of printing to a printer.
   public static void saveComponentAsEPS(Component c) throws IOException {
      // Find a factory object for printing Printable objects to PostScript.
      DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE;
      String format = "application/postscript";
      StreamPrintServiceFactory factory = StreamPrintServiceFactory.lookupStreamPrintServiceFactories(flavor,
         format)[0];
      // Ask the user to select a file and open the selected file
      //JFileChooser chooser = new JFileChooser();
      JFileChooser chooser = OSPRuntime.getChooser();
      if(chooser.showSaveDialog(c)!=JFileChooser.APPROVE_OPTION) {
         return;
      }
      File f = chooser.getSelectedFile();
      FileOutputStream out = new FileOutputStream(f);
      // Obtain a PrintService that prints to that file
      StreamPrintService service = factory.getPrintService(out);
      // Do the printing with the method below
      printToService(c,service, null);
      // And close the output file.
      out.close();
   }

   public static void printComponent(Component c) {
      // Get a list of all printers that can handle Printable objects.
      DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE;
      PrintService[] services = PrintServiceLookup.lookupPrintServices(flavor, null);
      // Set some define printing attributes
      PrintRequestAttributeSet printAttributes = new HashPrintRequestAttributeSet();
      //printAttributes.add(OrientationRequested.LANDSCAPE); // landscape mode
      printAttributes.add(OrientationRequested.PORTRAIT);   // PORTRAIT mode
      printAttributes.add(Chromaticity.MONOCHROME);        // print in mono
      printAttributes.add(javax.print.attribute.standard.PrintQuality.HIGH); // highest resolution
      // Display a dialog that allows the user to select one of the
      // available printers and to edit the default attributes
      PrintService service = ServiceUI.printDialog(null, 100, 100, services, null, null, printAttributes);
      // If the user canceled, don't do anything
      if(service==null) {
         return;
      }
      // Now call a method defined below to finish the printing
      printToService(c, service, printAttributes);
    }

   public static void printToService(final Component c, PrintService service, PrintRequestAttributeSet printAttributes) {
      Printable printable = new PrintableComponent(c);
      DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE;
      Doc doc = new SimpleDoc(printable, flavor, null);
      DocPrintJob job = service.createPrintJob();
      final JOptionPane pane = new JOptionPane("Printing...", JOptionPane.PLAIN_MESSAGE);
      JDialog dialog = pane.createDialog(c, "Print Status");
      // This listener object updates the dialog as the status changes
      job.addPrintJobListener(new PrintJobAdapter() {

         public void printJobCompleted(PrintJobEvent e) {
            pane.setMessage("Printing complete.");
         }

         public void printDataTransferCompleted(PrintJobEvent e) {
            pane.setMessage("Document transfered to printer.");
         }

         public void printJobRequiresAttention(PrintJobEvent e) {
            pane.setMessage("Check printer: out of paper?");
         }

         public void printJobFailed(PrintJobEvent e) {
            pane.setMessage("Print job failed");
         }
      });
      // Show the dialog, non-modal.
      dialog.setModal(false);
      dialog.setVisible(true);
      // Now print the Doc to the DocPrintJob
      try {
         job.print(doc, printAttributes);
      } catch(PrintException e) {
         // Display any errors to the dialog box
         pane.setMessage(e.toString());
      }
    }

   /*
    * PrintableComponent copied from Java Examples in a Nutshell.
    * Copyright (c) 2004 David Flanagan.  All rights reserved.
    * This code is from the book Java Examples in a Nutshell, 3nd Edition.
    * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
    * You may study, use, and modify it for any non-commercial purpose,
    * including teaching and use in open-source projects.
    * You may distribute it non-commercially as long as you retain this notice.
    * For a commercial use license, or to purchase the book,
    * please visit http://www.davidflanagan.com/javaexamples3.
 */
   public static class PrintableComponent implements Printable {
      Component c;
      public PrintableComponent(Component c) {
         this.c = c;
      }

      // This method should print the specified page number to the specified
      // Graphics object, abiding by the specified page format.
      // The printing system will call this method repeatedly to print all
      // pages of the print job.  If pagenum is greater than the last page,
      // it should return NO_SUCH_PAGE to indicate that it is done.  The
      // printing system may call this method multiple times per page.
      public int print(Graphics g, PageFormat format, int pagenum) {
         // This implemenation is always a single page
         if(pagenum>0) {
            return Printable.NO_SUCH_PAGE;
         }
         // The Java 1.2 printing API passes us a Graphics object, but we
         // can always cast it to a Graphics2D object
         Graphics2D g2 = (Graphics2D) g;
         // Translate to accomodate the requested top and left margins.
         g2.translate(format.getImageableX(), format.getImageableY());
         // Figure out how big the drawing is, and how big the page
         // (excluding margins) is
         Dimension size = c.getSize();                    // component size
         double pageWidth = format.getImageableWidth();   // Page width
         double pageHeight = format.getImageableHeight(); // Page height
         // If the component is too wide or tall for the page, scale it down
         if(size.width>pageWidth) {
            double factor = pageWidth/size.width; // How much to scale
            g2.scale(factor, factor);             // Adjust coordinate system
            pageWidth /= factor;                  // Adjust page size up
            pageHeight /= factor;
         }
         if(size.height>pageHeight) { // Do the same thing for height
            double factor = pageHeight/size.height;
            g2.scale(factor, factor);
            pageWidth /= factor;
            pageHeight /= factor;
         }
         // Now we know the component will fit on the page.  Center it by
         // translating as necessary.
         g2.translate((pageWidth-size.width)/2, (pageHeight-size.height)/2);
         // Draw a line around the outside of the drawing area and label it
         g2.drawRect(-1, -1, size.width+2, size.height+2);
         // Set a clipping region so the component can't draw outside of
         // its won bounds.
         g2.setClip(0, 0, size.width, size.height);
         // Finally, print the component by calling its paint() method.
         // This prints the background, border, and children as well.
         // For swing components, if you don't want the background, border,
         // and children, then call printComponent() instead.
         c.paint(g);
         // Tell the PrinterJob that the page number was valid
         return Printable.PAGE_EXISTS;
      }
   }
}
